﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO.Ports;
using System.Windows.Forms;
using System.Runtime.InteropServices;


namespace DMEditor
{
    public class DMInterface
    {

        //protected MIDIPort _inPort = new MIDIPort();
        protected MIDIOutPort _outPort = new MIDIOutPort();

        public Dictionary<int, string> ports = new Dictionary<int, string>();
        public int currentPort = 0;

        protected bool _isOnline = true;
        public bool Online
        {
            get { return _isOnline; }
            set { _isOnline = value; }
        }

        protected bool _autoKitChange = true;
        public bool suppressNRPNSend = false;  // Set to true temporarily to keep us from sending NRPNS (UI changes)
        public bool autoKitChange
        {
            get { return _autoKitChange; }
            set { _autoKitChange = value; }
        }

        public DMInterface()
        {
            int numOutPorts = MIDIOutPort.OutputCount;

            MIDIOUTCAPS caps = new MIDIOUTCAPS();

            for (int i = 0; i < numOutPorts; ++i)
            {
                WINMM_DLL.midiOutGetDevCaps((UIntPtr)i, out caps, (uint)Marshal.SizeOf(typeof(MIDIOUTCAPS)));
                ports.Add(i, caps.szPname);
            }

            currentPort = numOutPorts - 1;
            _outPort.Open(currentPort);
            
        }

        ~DMInterface()
        {
            _outPort.Close();
        }

        public void Open(int i)
        {
            if (_outPort.isOpen) _outPort.Close();

            _outPort.Open(i);
            currentPort = i;

        }

        public void SendKitChange(int kit)
        {
            _outPort.SendKitChange(kit);
        }

        public void SendNRPN(NRPN msgToSend, int value)
        {
            SendNRPN(msgToSend, (byte)(value & 0x7F), (byte)((value & 0xFF80) >> 7));
        }

        public void SendNRPN(NRPN msgToSend, bool msgMSB)
        {
            //Program.LogForm.AddLogItem("Sending NRPN " + msgToSend + " / 0x" + msgMSB.ToString("X2") + " / 0x" + msgData.ToString("X2"));
            SendNRPN(msgToSend, (msgMSB == true) ? (byte)1 : (byte)0, 0);
        }

        public void SendNRPN(NRPN msgToSend, byte msgMSB, byte msgData = 0)
        {
            if (!Program.module.Online || Program.module.suppressNRPNSend) return;


            Program.LogForm.AddLogItem("Sending NRPN " + msgToSend + " / 0x" + msgMSB.ToString("X2") + " / 0x" + msgData.ToString("X2")); 
            _outPort.SendNRPN(msgToSend, msgMSB, msgData);
        }

        
        public void Close()
        {
            _outPort.Close();
        }

        public void UploadInstrument(int kit, int instrument)
        {
            // Switch to the correct kit
            Program.module.SendKitChange(kit + 100);

            // Switch to the correct instrument
            Program.module.SendNRPN(NRPN.KitInstrument, instrument);

            //Send this instrument
            for (int layer = 0; layer < 2; ++layer)
            {
                Program.module.SendNRPN(NRPN.ChangeLayer, layer);

                // Instrument
                Program.module.SendNRPN(NRPN.InstLayerPreset, Program.settings.soundset.globalIDs[Program.settings.kits[kit].instruments[instrument].instLayers[layer]._category, Program.settings.kits[kit].instruments[instrument].instLayers[layer]._sound]);

                // Levels
                Program.module.SendNRPN(NRPN.InstLayerLevel, Program.settings.kits[kit].instruments[instrument].instLayers[layer]._level);
                Program.module.SendNRPN(NRPN.InstLayerPan, Program.settings.kits[kit].instruments[instrument].instLayers[layer]._pan);
                Program.module.SendNRPN(NRPN.InstLayerDecay, Program.settings.kits[kit].instruments[instrument].instLayers[layer]._decay);
                Program.module.SendNRPN(NRPN.InstLayerAttack, Program.settings.kits[kit].instruments[instrument].instLayers[layer]._attack);

                // FX
                Program.module.SendNRPN(NRPN.InstReverbSend, Program.settings.kits[kit].instruments[instrument].instLayers[layer]._reverb);
                Program.module.SendNRPN(NRPN.InstFxSend, Program.settings.kits[kit].instruments[instrument].instLayers[layer]._effects);
                Program.module.SendNRPN(NRPN.InstCompSend, Program.settings.kits[kit].instruments[instrument].instLayers[layer].eq);

                // Tone
                Program.module.SendNRPN(NRPN.InstLayerSemitones, Program.settings.kits[kit].instruments[instrument].instLayers[layer]._semitone);
                Program.module.SendNRPN(NRPN.InstLayerFine, Program.settings.kits[kit].instruments[instrument].instLayers[layer]._finetune);
                Program.module.SendNRPN(NRPN.InstLayerFilter, Program.settings.kits[kit].instruments[instrument].instLayers[layer]._filter);
                Program.module.SendNRPN(NRPN.InstLayerFilterType, Program.settings.kits[kit].instruments[instrument].instLayers[layer].filtertype);

                // Dynamics
                Program.module.SendNRPN(NRPN.InstLayerVelDecay, Program.settings.kits[kit].instruments[instrument].instLayers[layer]._velDecay);
                Program.module.SendNRPN(NRPN.InstLayerVelFilter, Program.settings.kits[kit].instruments[instrument].instLayers[layer]._velFilter);
                Program.module.SendNRPN(NRPN.InstLayerVelLevel, (Program.settings.kits[kit].instruments[instrument].instLayers[layer]._velLevel));
                Program.module.SendNRPN(NRPN.InstLayerVelPitch, Program.settings.kits[kit].instruments[instrument].instLayers[layer]._velPitch);

                // Other
                Program.module.SendNRPN(NRPN.InstOutput, Program.settings.kits[kit].instruments[instrument].instLayers[layer].output);
                Program.module.SendNRPN(NRPN.InstPriority, Program.settings.kits[kit].instruments[instrument].instLayers[layer]._priority);
                Program.module.SendNRPN(NRPN.InstMuteGroup, Program.settings.kits[kit].instruments[instrument].instLayers[layer]._muteGroup);
                Program.module.SendNRPN(NRPN.InstIsMono, Program.settings.kits[kit].instruments[instrument].instLayers[layer].monoPlayback);

            }

            // Back to Layer A
            Program.module.SendNRPN(NRPN.ChangeLayer, 0);

            // Send MIDI settings
            Program.module.SendNRPN(NRPN.InstMidiChannel, Program.settings.kits[kit].instruments[instrument].midi.channel);
            Program.module.SendNRPN(NRPN.InstMidiNote, Program.settings.kits[kit].instruments[instrument].midi.note);
            Program.module.SendNRPN(NRPN.InstGateTime, Program.settings.kits[kit].instruments[instrument].midi.gateTime);
            Program.module.SendNRPN(NRPN.InstOffMode, Program.settings.kits[kit].instruments[instrument].midi.sendNoteOff);
            
            Program.module.SendNRPN(NRPN.InstChordMode, Program.settings.kits[kit].instruments[instrument].midi.chordMode);
            Program.module.SendNRPN(NRPN.InstXtraNotes0, Program.settings.kits[kit].instruments[instrument].midi.note2);
            Program.module.SendNRPN(NRPN.InstXtraNotes1, Program.settings.kits[kit].instruments[instrument].midi.note3);
            Program.module.SendNRPN(NRPN.InstXtraNotes2, Program.settings.kits[kit].instruments[instrument].midi.note4);

            System.Threading.Thread.Sleep(100); // Sleep for a little bit to let us flush the buffer
        }

        public void UploadKit(int kit)
        {
            // Switch to the correct kit
            Program.module.SendKitChange((byte)(kit + 100));

            for (int i = 0; i < Program.settings.kits[kit].instruments.Length; ++i)
            {
                Program.LogForm.AddLogItem("Uploading instrument " + i);
                UploadInstrument(kit, i);
            }

            // Switch back to Instrument 0
            Program.module.SendNRPN(NRPN.KitInstrument, 0);
        }
    }

    public class MIDIOutPort
    {
        private IntPtr handle;
        protected bool _isOpen = false;
        public bool isOpen
        {
            get { return _isOpen; }
        }

        public MIDIOutPort()
        {
            handle = IntPtr.Zero;
        }

        public static int InputCount
        {
            get { return WINMM_DLL.midiInGetNumDevs(); }
        }


        public static int OutputCount
        {
            get { return WINMM_DLL.midiOutGetNumDevs(); }
        }


        public bool Close()
        {
            bool result = WINMM_DLL.midiOutClose(handle) == WINMM_DLL.MMSYSERR_NOERROR;
            handle = IntPtr.Zero;
            return result;
        }

        public bool Open(int id)
        {
            bool val = WINMM_DLL.midiOutOpen(out handle, id, null, IntPtr.Zero, WINMM_DLL.CALLBACK_NULL) == WINMM_DLL.MMSYSERR_NOERROR;

            _isOpen = val;
            return val;
        }


        // Change the current kit to 'kit'
        public void SendKitChange(int kit)
        {
            byte[] msg = new byte[4];

            bool isBank2 = false;
            if (kit > 200 || kit < 1) throw new IndexOutOfRangeException(); 
            
            if (kit > 99)
            {
                isBank2 = true;
                kit -= 100;
            }

            // First make sure we're in the right bank
            msg[0] = 0xB9;
            msg[1] = 32;
            msg[2] = (byte)(isBank2 ? 1 : 0);
            if (WINMM_DLL.midiOutShortMsg(handle, BitConverter.ToInt32(msg, 0)) != WINMM_DLL.MMSYSERR_NOERROR)
                throw new InvalidOperationException();


            msg[0] = 0xC9;
            msg[1] = (byte)kit;
            msg[2] = 0;
            if (WINMM_DLL.midiOutShortMsg(handle, BitConverter.ToInt32(msg, 0)) != WINMM_DLL.MMSYSERR_NOERROR)
                throw new InvalidOperationException();
        }


        public void SendNRPN(NRPN msgToSend, byte msgLSB, byte msgMSB = 0)
        {
            byte[] msg = new byte[4];

            // Send the LSB first
            msg[0] = 0xB9;
            msg[1] = 98;
            msg[2] = (byte)msgToSend;
            if (WINMM_DLL.midiOutShortMsg(handle, BitConverter.ToInt32(msg, 0)) != WINMM_DLL.MMSYSERR_NOERROR)
                throw new InvalidOperationException();

            // Send the MSB
            msg[0] = 0xB9;
            msg[1] = 6;
            msg[2] = msgMSB;
            if (WINMM_DLL.midiOutShortMsg(handle, BitConverter.ToInt32(msg, 0)) != WINMM_DLL.MMSYSERR_NOERROR)
                throw new InvalidOperationException();

            // Now send the LSB
            msg[0] = 0xB9;
            msg[1] = 38;
            msg[2] = msgLSB;
            if (WINMM_DLL.midiOutShortMsg(handle, BitConverter.ToInt32(msg, 0)) != WINMM_DLL.MMSYSERR_NOERROR)
                throw new InvalidOperationException();


        }
    }

    public enum NRPN
    {
        KitLevel = 0,
        Icon = 1,
        MidiProgChan = 2, 
        MidiProgNum = 3,
        MidiMSB = 4,
        MidiLSB = 5,
        ReverbPreset = 6,
        ReverbSize = 7,
        ReverbColor = 8,
        ReverbLevel	= 9,
        DelayPreset	= 10,
        DelayTimeLeft	= 11,
        DelayTimeRight	= 12,
        DelayLevel	= 13,
        CompressorPreset	= 14,
        CompressRatio	= 15,
        CompressLevel	= 16,
        LowEQFreq	= 17,
        HighEQFreq	= 18,
        LowEQGain	= 19,
        HighEQGain	= 20,
        DelayFeedbackLeft	= 21,
        DelayFeedbackRight	= 22,
        DelayDepth	= 23,
        DelayRate	= 24,
        DelayDamp	= 25,
        InstReverbSend	= 30,
        InstFxSend	= 31,
        InstCompSend	= 32,
        InstOutput	= 33,
        InstPriority	= 34,
        InstMuteGroup	= 35,
        InstIsMono	= 36,
        InstMidiChannel	= 37,
        InstMidiNote	= 38,
        InstGateTime	= 39,
        InstOffMode	= 40,
        InstChordMode	= 41,
        InstXtraNotes0	= 42,
        InstXtraNotes1	= 43,
        InstXtraNotes2	= 44,
        InstXtraNotes3	= 45,
        InstXtraNotes4	= 46,
        InstLayerPreset	= 60,
        InstLayerLevel	= 61,
        InstLayerPan	= 62,
        InstLayerDecay	= 63,
        InstLayerAttack	= 64,
        InstLayerSemitones	= 66,
        InstLayerFine	= 67,
        InstLayerFilter	= 68,
        InstLayerFilterType	= 69,
        InstLayerVelDecay	= 70,
        InstLayerVelPitch	= 71,
        InstLayerVelFilter	= 72,
        InstLayerVelLevel	= 73,
        KitInstrument	= 118,
        ChangeLayer	= 122,
    }


    internal static class WINMM_DLL
    {
        internal const int MMSYSERR_NOERROR = 0;
        internal const int CALLBACK_FUNCTION = 0x00030000;
        internal const int CALLBACK_NULL = 0x00000000;

        internal delegate void MidiInProc(IntPtr hMidiIn, int wMsg, IntPtr dwInstance, int dwParam1, int dwParam2);
        internal delegate void MidiOutProc(IntPtr hMidiOut, int wMsg, IntPtr dwInstance, int dwParam1, int dwParam2);

        #region MIDI Out
        [DllImport("winmm.dll")]
        internal static extern int midiOutGetNumDevs();

        [DllImport("winmm.dll")]
        internal static extern int midiOutGetDevCaps(UIntPtr uDeviceID, out MIDIOUTCAPS caps, uint count);

        [DllImport("winmm.dll")]
        internal static extern int midiOutOpen(out IntPtr lphMidiOut, int uDeviceID, MidiOutProc dwCallback, IntPtr dwCallbackInstance, int dwFlags);

        [DllImport("winmm.dll")]
        internal static extern int midiOutClose(IntPtr hMidiOut);
        
        [DllImport("winmm.dll")]
        internal static extern int midiOutShortMsg(IntPtr hMidiOut, int message);
        #endregion

        #region MIDI In
        [DllImport("winmm.dll")]
        internal static extern int midiInGetNumDevs();

        [DllImport("winmm.dll")]
        internal static extern int midiInClose(IntPtr hMidiIn);

        [DllImport("winmm.dll")]
        internal static extern int midiInOpen(out IntPtr lphMidiIn, int uDeviceID, MidiInProc dwCallback, IntPtr dwCallbackInstance, int dwFlags);

        [DllImport("winmm.dll")]
        internal static extern int midiInStart(IntPtr hMidiIn);

        [DllImport("winmm.dll")]
        internal static extern int midiInStop(IntPtr hMidiIn);
        #endregion

    }

    [StructLayout(LayoutKind.Sequential)]
    struct MIDIOUTCAPS
    {
        public ushort wMid;
        public ushort wPid;
        public uint vDriverVersion;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string szPname;
        public ushort wTechnology;
        public ushort wVoices;
        public ushort wNotes;
        public ushort wChannelMask;
        public uint dwSupport;
    }

}
